Последние изменения - Поиск:

edit SideBar

ООП

Главное, что нужно знать об ООП - что это не способ решения всех проблем и не серебряная пуля. А всего лишь инструмент, достаточно несовершенный и выходящий из моды.

  • Два понятия ООП: автономные объекты, сообщения, позднее связывание - из Smalltalk; и наследование, инкапсуляция, полиморфизм - из Модулы 2. В основном под ООП понимают второй вариант, хотя он появился позднее.
  • Объект - это существительное, метод - это глагол. Сказать "функция принадлежит объекту" - это то же, что сказать, "глагол принадлежит существительному". Однако в человеческих языках и по природе вещей это не так. Существительное и глагол ортогональны. Принадлежность функции объекту делает возможной удобную запись подлежащее.сказуемое, но не всегда естественна. В частности, ортогональность существительного и глагола приходится выражать через средства полиморфизма - интерфейсы и наследование, или делать глаголы обычными функциями. Но принадлежность глагола существительном - не является неотъемлемым свойством ООП. Понятие о родовых функциях (CLOS). Равно как и сказать "всё есть объект" - это искажение реальности, то же, что сказать "глагол - это существительное".
  • ООП среди других парадигм. Реляционная парадигма. Полезность SQL сервера состоит в том, что он автоматизирует решение задач (компьютер делает работу за человека). Это свойство не парадигмы, но инструмента. Чистое ФП. Его полезность - упрощается анализ программ за счёт отсутствия побочных эффектов. Кодогенерация - позволяет сокращать труд по созданию и поддержке шаблонного кода за счёт его автоматизации. Подумать, чем полезна ООП? Что в этом случае автоматизируется? Мой ответ - по сути, ничего не автоматизирует. ООП - это только лишь определённая дисциплина, которая заставляет человека следовать определённым соглашениям. Может иметь смысл, для разделения работы - поставщик предоставляет класс, а пользователь его наследует. Но ООП - не единственная возможная дисциплина.
  • ООП ортогонально статической типизации (Smalltalk, Python, Common Lisp). Работа с объектами в статической и динамической типизации. Преимущества статической типизации - меньшая неоднозначность текста программы (и ещё другие нагуглить), повышенная эффективность. Преимущества динамической - повышение выразительности и сокращение объёма исходника. Осознать, что виртуальный метод - это частный случай динамической типизации.
  • Как люди жили и написали Linux, Oracle до появления ООП? Указатели на функции как альтернатива виртуальным методам. Инкапсуляция модулями как альтернатива инкапсуляции классами. Встраивание записей в Си и Голанг и композиция как альтернатива наследованию.
  • Классы и типы как разные понятия. Integer может быть классом, а может и не быть. Числовая башня не помещается в ООП. Массивы не помещаются в ООП (т.к. являются параметрическими типами). Системы типов в С++, C#, Haskell и Common Lisp - вроде как достаточно общая картина? Кто лучше знает программирование - поправьте.
  • Шаблон проектирования Visitor - не закон природы, а лишь следствие из того, как реализовано ООП конкретно в C++ и Java. Понятие о множественной диспетчеризации.
  • ООП в стиле С++ не так уж сложно реализовать. Реализовать эрзац-ООП на Си с помощью макросов. Должны быть записи с тегом типа, таблицы виртуальных методов. Будет многословно, но по смыслу должно получиться то же, что в С++. Продвинутое упражнение - реализовать ООП со множественной диспетчеризацией, как в CLOS.
  • Отличие наследования от композиции. Рекомендуют применять композицию вместо наследования. Проблема хрупкого базового класса. Но у наследования есть преимущества, которые нельзя игнорировать. В наследовании - общее время жизни, одновременная инициализация, один кусок памяти. Пространство имён методов может не совпадать (при множественном наследовании в С++ можно указывать конкретного предка при вызове метода). Внедрение зависимостей ведь вроде тоже сюда относится?
  • Принцип Лисковой. Перекрытие потомком реализованного предком виртуального метода нарушает его. Добавление поля к классу нарушает его. А нарушение принципа Лисковой затрудняет анализ программы. Отсюда возникают интерфейсы как основной случай ООП. В современных языках (Rust, Golang) вовсе отказались от наследования или сделали его малозаметным (включение). Проблемы многословности и распухания двоичного кода в Rust, связанные с этим выбором.
  • Варианты использования ООП без инкапсуляции (CL, а также Scala c public по умолчанию). Противоречие между инкапсуляцией и рефлексией (в Golang только public поля записи можно сериализовать в Json). RTTI. Взлом инкапсуляции через определение идентичного класса и опасное приведение типа.
  • Snit как пример ООП без объектов. Автоматическое делегирование методов средствами метапрограммирования. Средства объектного моделирования (Rational Rose, ныне что-то про неё позабыли, а я успешно прошёл мимо). Осознание, что делать частное поле и потом к нему две функции - это наказание, и плох тот язык, который его не автоматизирует для частных случаев.
  • Насколько я знаю, современные руководства по проектированию говорят о нежелательности глубоких иерархий. В реальности многие крупные проекты содержат глубокие иерархии. В теории, теория и практика согласуются, но на практике они зачастую сильно отличаются.
  • Наследование реализации и таблицы виртуальных методов как способ оптимизации программ. Но ООП находится на более высоком уровне абстракции. Таким образом, в С++ наследование представляет из себя смешение уровней абстракции, это не хорошо и не плохо, но нужно это понимать.
  • Множественное наследование. Я не эксперт по ООП, но вроде как в С++ оно реализовано наиболее общим способом, мне нравится. Ограниченные способы множественного наследования - примеси. Разобраться в терминологии, что называется mixin и trait в разных языках - терминология противоречивая. Составить табличку, в неё должны входить, как минимум PHP, Scala и Haskell (тайпклассы - это интерфейсы). Знать, что в Java ввели множественное наследование спустя ~20 лет, а до этого говорили, что оно не нужно.
  • Проблема алмаза (diamond inheritance, наследование по нескольким путям). Изучить историю борьбы с ним в Scala. Его закапывают, а оно выползает в новом месте. Вероятно, что-то не так с самой идеей.
  • Сортировка методов при множественном наследовании (забыл термин). Применяется в Scala. Вообще говоря, лишняя сущность, произвольное соглашение, созданное только, чтобы искусственным путём разрешить неоднозначность. И тут должно снова закрасться подозрение, что в ООП изначально не всё было гладко.
  • Осознание, что шаблоны классов, дженерики и прочее - это механизм, отдельный от ООП. Сделать вывод о том, что ООП - не такой уж общий механизм.
  • Проблема неродственности TemplateClass<T> для разных T. Ковариантность и контравариантность. По-моему, в С# сделано наиболее толково. Неродственность реализаций интерфейсов как другой вид той же проблемы. Понятие о type erasure. Проблема приведения типа от интерфейса к реализации. Разобрать эту проблему в Rust и Golang. Как в Rust - не знаю/не помню (кажется, там плохо), а в Golang хорошо: по интерфейсу через рефлексию можно узнать тип объекта, реализующего этот интерфейс.
  • Синглтоны как аналоги модулей. Если вам надо вскипятить чай, налейте в чайник воды и поставьте на огонь. Если в чайнике уже есть вода, вылейте её и задача сводится к предыдущему варианту.
  • Перегрузка операторов не относится к ООП и может быть основана на любой системе типов, не обязательно на ООП.
  • Понятие шаблона проектирования не связано с ООП и может относиться к любому проектированию. ВОобще возникло из архитектуры зданий.
Править - История - Печать - Последние изменения - Поиск
Редакция от 30.03.2019 11:33